//#define FULL_TRACE

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Threading.Tasks;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using alib.Debugging;
using alib.Reflection;

namespace agree.schema
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public abstract class schema_prop : PropertyInfo, _PropertyInfo, ICustomAttributeProvider
	{
		public schema_prop(Schema sch, PropertyInfo pi)
		{
			this.pi = pi;
			this.metadata_token = pi.MetadataToken;
			this.sch = sch;
			this.name = pi.Name;
			//this.t_value = sch.promote_type(pi.PropertyType);
			this.t_value = pi.PropertyType;
			this.t_reflected = pi.ReflectedType;
			this.t_declaring = pi.DeclaringType;
			this.prop_attribs = pi.Attributes;

			//PropertyAttributes.HasDefault

			this.base_getter = pi.GetGetMethod(true);
			this.base_setter = pi.GetSetMethod(true);
			if (base_getter == null && base_setter == null)
				throw new Exception();

			if (t_value.IsValueType)
			{
				//this.DefaultValue = t_value.GetConstructor(null).Invoke(null);
				this.DefaultValue = Activator.CreateInstance(t_value);
			}
			else
				this.DefaultValue = null;
		}
		protected readonly PropertyInfo pi;
		readonly int metadata_token;
		public readonly Schema sch;
		readonly String name;
		readonly PropertyAttributes prop_attribs;
		public readonly Type t_value;
		readonly Type t_reflected;
		readonly Type t_declaring;
		public readonly Object DefaultValue;
		readonly MethodInfo base_getter, base_setter;

		protected void do_propchange(Object obj, Object value)
		{
			sch.find_type(obj.GetType()).KnownType.do_prop_cbs(obj, value);
		}

		////////////////////////////////////////////////////////
		/// 
		public override Object GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
		{
			return base_getter.Invoke(obj, invokeAttr, binder, index, null);
		}
		public override void SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
		{
			int c;
			var args = new Object[(c = index == null ? 0 : index.Length) + 1];
			for (int i = 0; i < c; i++)
				args[i] = index[i];
			args[c] = value;

			base_setter.Invoke(obj, invokeAttr, binder, args, culture);

			do_propchange(obj, value);
		}
		/// 
		////////////////////////////////////////////////////////

		public bool TryGetItemValue(Object item, out Object value)
		{
			if (!sch._typeof<ISchemaObj>().IsAssignableFrom(sch.get_type(item)))
				throw new Exception();

			if ((value = GetValue(item, BindingFlags.Default, null, null, null)) != null)
				return true;

			value = this.DefaultValue;
			return false;
		}

		public Object FindTracePropertyValue(ISchemaObj item)
		{
			if (item == null)
				return this.DefaultValue;

			Object o;
			if (this.TryGetItemValue(item, out o))
				return o;

			IIdentity iid;
			if ((iid = item as IIdentity) != null)
				return FindTracePropertyValue(iid);

			return this.DefaultValue;
		}

		public Object FindTracePropertyValue(IIdentity item)
		{
			while (item != null)
			{
				Object o;
				if (this.TryGetItemValue(item, out o))
					return o;

				/// for materialized containers, we are looking for any contained item which matches this property type, 
				/// regardless of whether the container exposes that item with with this property descriptor. (behavior 
				/// subject to change if it ends up being weird)
				var arr = item as IIdentList<IIdentity>;
				if (arr != null)
					for (int i = 0; i < arr.Count; i++)
						if (t_value.IsAssignableFrom(arr[i].GetType()))
							return arr[i];

				item = item.Trace;
			}
			return this.DefaultValue;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// properties

		public sealed override MemberTypes MemberType { [DebuggerStepThrough] get { return MemberTypes.Property; } }

		public sealed override String Name { [DebuggerStepThrough] get { return name; } }

		public sealed override int MetadataToken { [DebuggerStepThrough] get { return metadata_token; } }

		public sealed override PropertyAttributes Attributes { [DebuggerStepThrough] get { return prop_attribs; } }

		public sealed override Type PropertyType { [DebuggerStepThrough] get { return t_value; } }

		public sealed override Type ReflectedType { [DebuggerStepThrough] get { return t_reflected; } }

		public sealed override Type DeclaringType { [DebuggerStepThrough] get { return t_declaring; } }


		public override bool CanRead
		{
			get
			{
				dbg.write(this.GetType().Name + ".CanRead... ");
				try
				{
					var x = pi.CanRead;
					dbg.objinf(x);
					return x;
				}
				catch (Exception ex) { dbg.ex_info(ex); throw; }
			}
		}
		public override bool CanWrite
		{
			get
			{
				dbg.write(this.GetType().Name + ".CanWrite... ");
				try
				{
					var x = pi.CanWrite;
					dbg.objinf(x);
					return x;
				}
				catch (Exception ex) { dbg.ex_info(ex); throw; }
			}
		}

		public override Module Module
		{
			get
			{
				dbg.write(this.GetType().Name + ".Module... ");
				try
				{
					var x = pi.Module;
					dbg.objinf(x);
					return x;
				}
				catch (Exception ex) { dbg.ex_info(ex); throw; }
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// methods

		public override MethodInfo[] GetAccessors(bool f_non_public)
		{
			var x = pi.GetAccessors(f_non_public);
			return x;
		}
		public override ParameterInfo[] GetIndexParameters()
		{
			var x = pi.GetIndexParameters();
			return x;
		}

		public sealed override MethodInfo GetMethod { get { return GetGetMethod(true); } }

		public override MethodInfo GetGetMethod(bool f_non_public)
		{
			var x = pi.GetGetMethod(f_non_public);
			return x;
		}

		public sealed override MethodInfo SetMethod { get { return GetSetMethod(true); } }

		public override MethodInfo GetSetMethod(bool f_non_public)
		{
			var x = pi.GetSetMethod(f_non_public);
			return x;
		}
		[DebuggerStepThrough]
		public sealed override Object GetValue(Object obj, Object[] index)
		{
			return GetValue(obj, 0, null, index, null);
		}
		[DebuggerStepThrough]
		public sealed override void SetValue(Object obj, Object value, Object[] index)
		{
			SetValue(obj, value, 0, null, index, null);
		}
		public override Type[] GetRequiredCustomModifiers()
		{
			dbg.write(this.GetType().Name + ".GetRequiredCustomModifiers... ");
			try
			{
				var x = pi.GetRequiredCustomModifiers();
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}
		public override Type[] GetOptionalCustomModifiers()
		{
			dbg.write(this.GetType().Name + ".GetOptionalCustomModifiers... ");
			try
			{
				var x = pi.GetOptionalCustomModifiers();
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}

		public override IEnumerable<CustomAttributeData> CustomAttributes
		{
			get
			{
				dbg.write(this.GetType().Name + ".CustomAttributes... ");
				try
				{
					var x = pi.CustomAttributes;
					dbg.objinf(x);
					return x;
				}
				catch (Exception ex) { dbg.ex_info(ex); throw; }
			}
		}
		public override Object[] GetCustomAttributes(bool inherit)
		{
			dbg.write(this.GetType().Name + ".GetCustomAttributes... ");
			try
			{
				var x = pi.GetCustomAttributes(inherit);
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}
		public override Object[] GetCustomAttributes(Type T_attr_typ, bool inherit)
		{
			dbg.write(this.GetType().Name + ".GetCustomAttributes... ");
			try
			{
				var x = pi.GetCustomAttributes(T_attr_typ, inherit);
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}
		public override bool IsDefined(Type T_attr_typ, bool inherit)
		{
			dbg.write(this.GetType().Name + ".IsDefined... ");
			try
			{
				var x = pi.IsDefined(T_attr_typ, inherit);
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}
		public override IList<CustomAttributeData> GetCustomAttributesData()
		{
			dbg.write(this.GetType().Name + ".GetCustomAttributesData... ");
			try
			{
				var x = pi.GetCustomAttributesData();
				dbg.objinf(x);
				return x;
			}
			catch (Exception ex) { dbg.ex_info(ex); throw; }
		}

		public override String ToString()
		{
			return String.Format("{0} .{1}; typeof({2})", this.GetType().Name, this.name, t_value._Name());
		}
	};
}
